#!usr/bin/env python
# -*- coding:utf-8 -*-

'''
Created on 30 mai 2014

@author: Alexandre Neuville
'''

from datetime import datetime
from functools import total_ordering
from json import load, dump
from logging import getLogger, Formatter, StreamHandler, DEBUG
from sys import stdout

from pypyodbc import connect


@total_ordering
class ConnectionInformation(object):

    def __init__(self, number, user=None, ip=None, reqtype=None, blocked=None, lock=None, time=None, sql=None):
        self.number = number
        self.user = user
        self.ip = ip
        self.reqtype = reqtype
        self.blocked = blocked
        self.lock = lock
        self.sql = sql
        self.time = time
        self.connection = None

    def _is_valid_operand(self, other):
        return (hasattr(other, "time") and hasattr(other, "connection"))

    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return (self.time, self.number) == (other.time, other.number)

    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return (self.time, self.number) < (other.time, other.number)

    def __repr__(self):
        return "ConnectionInformation(number=%r,user=%r,ip=%r,reqtype=%r,blocked=%r,lock=%r,time=%r,sql=%r)" % \
            (self.number, self.user, self.ip, self.reqtype, self.blocked, self.lock, self.time, self.sql)

    def __str__(self):
        return "[ %d, %s, %s, %s, %s, %s, %s, %s]" % \
            (self.number, self.user, self.ip, self.reqtype, self.blocked, self.lock, self.time, self.sql)


class Database(object):

    SQL_INFO = "select info.Number 'number', \
                       info.Userid 'user', \
                       info.NodeAddr 'ip', \
                       info.ReqType 'reqtype', \
                       info.BlockedOn 'blocked', \
                       info.LockTable 'lock', \
                       act.LastStatement 'sql', \
                       act.LastReqTime 'time', \
                       (select connection_property('Number')) 'connection' \
                from dbo.sa_conn_info() \"info\", \
                     dbo.sa_conn_activity() \"act\" \
                where info.Number = act.Number \
                  and info.Number <> (select connection_property('Number')) \
                  and act.DBNumber = (select connection_property('DBNumber'))"
    SQL_DISCONNECT = "drop connection %d"

    def __init__(self, dsn, uid=None, pwd=None):
        self.dsn = dsn
        self.uid = uid
        self.pwd = pwd

        connStr = ["DSN="]
        connStr.append(dsn)
        if uid:
            connStr.append(";UID=")
            connStr.append(uid)
        if pwd:
            connStr.append(";PWD=")
            connStr.append(pwd)
        connStr.append(";")
        self.__dsn = ''.join(connStr)
        self.__conn = None
        self.__logger = LoggerFactory.getStdLogger(self)

    def __enter__(self):
        self.__conn = connect(connectString=self.__dsn, autocommit=True)
        self.__logger.debug("Database connected with parameters : %s", self.__dsn)
        return self

    def __exit__(self, unusedType, unusedValue, unusedTb):
        if self.__conn and self.__conn.connected:
            self.__conn.close()
            self.__logger.debug("Database disconnected")

    def __del__(self):
        if self.__conn and self.__conn.connected:
            self.__conn.close()

    def _isConnected(self):
        if not self.__conn:
            return False
        return self.__conn.connected

    def getDatabaseInformations(self):
        if not self._isConnected():
            self.__logger.error("Database not connected")
            return None

        with self.__conn.cursor() as cr:
            self.__logger.debug("Execute sql : %s", Database.SQL_INFO)
            rows = cr.execute(Database.SQL_INFO)

            infos = []
            for row in rows:
                info = ConnectionInformation(row[0])
                info.user = row[1]
                info.ip = row[2]
                info.reqtype = row[3]
                info.blocked = row[4]
                info.lock = row[5]
                info.sql = row[6]
                info.time = datetime.strptime(row[7], "%Y-%m-%j %H:%M:%S.%f")
                info.connection = row[8]

                self.__logger.debug("Readed connection info : %s", info)
                infos.append(info)

            infos.sort()
            return infos

    def ejectConnection(self, number):
        if not self._isConnected():
            self.__logger.error("Database not connected")
            return None

        with self.__conn.cursor() as cr:
            self.__logger.debug("Execute sql : %s with parameter %d", Database.SQL_DISCONNECT, number)
            cr.execute(Database.SQL_DISCONNECT % number)

    def __repr__(self):
        return "Database(dsn=%r)" % (self.dsn)


class LoggerFactory(object):

    __STD_LOGGERS = {}
    __STD_LVL = None
    __FILE_LVL = None
    __FORMAT = None
    __FILE_FORMAT = None

    @classmethod
    def configure(cls, level=DEBUG, fmt="%(message)s", fileLevel=None, fileFmt=None):
        if not fileLevel:
            fileLevel = level
        if not fileFmt:
            fileFmt = fmt

        cls.__FORMAT = fmt
        cls.__STD_LVL = level
        cls.__FILE_FORMAT = fileFmt
        cls.__FILE_LVL = fileLevel

    @classmethod
    def getStdLogger(cls, caller):
        key = caller.__class__.__name__
        logger = cls.__STD_LOGGERS.get(key, None)
        if not logger:
            formatter = Formatter(cls.__FORMAT)

            handler = StreamHandler(stdout)
            handler.setLevel(cls.__STD_LVL)
            handler.setFormatter(formatter)

            logger = getLogger(key)
            logger.setLevel(cls.__STD_LVL)
            logger.addHandler(handler)

            cls.__STD_LOGGERS[key] = logger

        return logger


class JSONConfig(object):

    PWD = "pwd"

    def __init__(self, configFilename):
        self.configFilename = configFilename
        self.__logger = None
        self.config = None

    def __enter__(self):
        self.loadConfig()
        logLevel = self.getMParameter("logging.level")
        logFormat = self.getMParameter("logging.format")
        LoggerFactory.configure(logLevel, logFormat)
        self.__logger = LoggerFactory.getStdLogger(self)
        self.__logger.debug("Configuration loaded from '%s'", self.configFilename)
        return self

    def __exit__(self, unusedType, unusedValue, unusedTb):
        try:
            self.saveConfig()
            return False
        except:
            self.__logger.error("Configuration not saved in '%s'", self.configFilename)
            return False

    def saveConfig(self):
        with open(self.configFilename, mode="w", encoding="utf-8") as cf:
            dump(self.config, cf, indent=4, sort_keys=True)
            self.__logger.debug("Configuration saved in '%s'", self.configFilename)

    def loadConfig(self):
        with open(self.configFilename, mode="r", encoding="utf-8") as cf:
            self.config = load(cf)
            if self.__logger:
                self.__logger.debug("Configuration loaded from '%s'", self.configFilename)

    def getParameter(self, key):
        c = self.config
        keys = key.split(".")
        for k in keys:
            c = c.get(k, None)
            if c is None:
                return None

        return c

    def getMParameter(self, key):
        c = self.getParameter(key)
        if c is None:
            raise NoParameterError("Parameter " + key + " not found in configuration")
        return c

    def setParameter(self, key, value):
        self.config[key] = value


class NoParameterError(Exception):
    pass
